/***************************************************************************
 *   oboe.controllers.jml.helpers.JMLNode                                  *
 *   Copyright (C) 2011 by Felipe Manga                                    *
 *   lufeboma@gmail.com                                                    *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

package oboe.controllers.jml.helpers
{
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.geom.Matrix;
	import flash.system.ApplicationDomain;
	import flash.utils.Dictionary;
	
	import oboe.controllers.jml.JMLController;
	import oboe.controllers.jml.events.JMLEvent;
	import oboe.core.Controller;
	
	public class JMLNode extends Controller {
		private var asset:DisplayObject;
		private var parent:JMLController;
		private var jmlfunctions:Dictionary; 
		private var jmlconstants:Dictionary;
		
		private static const stack:Vector.<JMLNode> = new Vector.<JMLNode>();
				
		public function JMLNode( head:String, jml:Object, parent:JMLController )
		{
			var parts:Array = head.split(":");
			var type:String;
			var id:String;
			this.parent = parent;
			if( parts.length>0 ) type = parts[0];
			if( parts.length>1 ) id = parts[1];
			stack.push( this );
			
			super( createDisplayObject(type) );
			
			this.asset = this.getDisplayObject();

			if( id )
			{
				parent.newIdIndex( id, this );
				this.asset.name = id;
			}

			this.jmlfunctions = new Dictionary();
			this.addJMLFunctions( this.jmlfunctions );
			this.jmlconstants = new Dictionary();
			this.addJMLConstants( this.jmlconstants );
			
			var result:Object = {};
			
			if( parent.stylejml )
			{
				if( ("->" + type) in parent.stylejml ) this.processJML( parent.stylejml["->"+type], result );
				if( ("=>" + id) in parent.stylejml ) this.processJML( parent.stylejml["=>"+id], result );
				this.processJML( jml, result );
				if( ("<-" + type) in parent.stylejml ) this.processJML( parent.stylejml["<-"+type], result );
				if( ("<=" + id) in parent.stylejml ) this.processJML( parent.stylejml["<="+id], result );
			}
			else
			{
				this.processJML( jml, result );
			}
			this.asset.dispatchEvent( new JMLEvent("JMLComplete", result ) );
		}
		
		public function jmlRoot():JMLController
		{
			return this.parent;
		}
		
		[slot] public function setProperty( name:String, value:Object ):void
		{
			this.asset[name] = value;
		}
		
		public function getAsset():DisplayObject
		{
			return this.asset;
		}
		
		public function getProperty( name:String ):Object
		{
			return this.asset[name];
		}

		public function slot_getProperty( name:String, out:Array ):void
		{
			out.push( this.asset[name] );
		}
		
		[slot] public function setScrollbarVisible( state:Boolean ):void
		{
			if( this.asset is JMLClipper ) JMLClipper( this.asset ).setScrollbarVisible( state );
		}
		
		protected function addJMLFunctions( dict:Dictionary ):void
		{
			for each( var node:JMLNode in stack )
			{
				for( var key:String in node.jmlfunctions )
				{
					dict[key] = node.jmlfunctions[key];
				}
			}
			dict["shortSignal"] = this.shortSignal;
			dict["signal"] = this.signal;
			dict["signalDown"] = this.signalDown;
			dict["signalUp"] = this.signalUp;
			dict["message"] = this.message;
			dict["root.message"] = function ( ...params ):void
			{
				parent.message.apply( parent, params );
			}
			dict["set"] = function (k:String, v:Object):void
			{ 
				var r:Array = resolve(k);
				r[0][ r[1] ] = v; 
			};
		}
		
		protected function addJMLConstants( dict:Dictionary ):void
		{
			for each( var node:JMLNode in stack )
			{
				for( var key:String in node.jmlconstants )
				{
					dict[key] = node.jmlconstants[key];
				}
			}
		}

		protected function createDisplayObject( type:String ):DisplayObject
		{
			return parent.getDisplayObjectForType( type );			
		}
		
		private function resolve( key:String ):Array
		{
			var funcParts:Array = key.split(" ");
			var keyParts:Array = funcParts[0].split(".");
			var pp:Object = this.asset;
			var i:int=0;
			if( keyParts[i] == "this" )
			{
				pp = this;
				i=1;
			}
			for( ; i<keyParts.length-1; ++i )
			{
				pp = pp[ keyParts[i] ];
			}
			var funcName:String = keyParts[i]; 
			if( funcParts.length>1 )
			{
				funcParts[0] = funcName;
				funcParts.unshift( pp );
				return funcParts;
			}
			return [pp, funcName];
		}
		
		private function processJML( jml:Object, result:Object ):void
		{
			if( jml is Array )
			{
				for each( var j:Object in jml )
				{
					this.processJML( j, result );
				}
				return;
			}
			
			for( var key:String in jml )
			{
				var val:Object = jml[key];
				var r:Array = this.resolve( key );
				var pp:Object = r[0];
				var nkey:String = r[1];
				var param:Object;
				
				if( nkey in pp || pp is Array || pp.constructor == Object )
				{
					if( val is String )
					{
						if( parent.translation && val.length > 2 && String(val).substr(0, 2) == "$$" && val in parent.translation )
						{
							val = parent.translation[val];
						}
						else if( val in this.jmlconstants )
						{
							val = this.jmlconstants[ val ];
						}
						
						pp[nkey] = val;
						result[ nkey ] = val;
					}
					else if( val is Boolean || val is int || val is Number || (val is Array && !(pp[nkey] is Function)) )
					{
						pp[nkey] = val;
						result[ nkey ] = val;
					}
					else if( val is Array )
					{
						pp[nkey].apply( pp, val );
					}
				}
				else if( key.substr(0, 5) == "@pipe" )
				{
					this.addPipe( String(val), function(...args):void{} );
				}
				else if( key.indexOf(":") > -1 )
				{
					this.addChild( result[ key ] = parent.getControllerForType( key.split(":")[0], key, val ) );
				}
				else if( key == "@callback" )
				{
					if( val[0] is Array )
					{
						for each( call in val )
						{
							var oldCall1:String = call[1];
							if( call[1] in this.jmlfunctions )
							{
								call[1] = this.jmlfunctions[ call[1] ];
							}
							else
							{
								call[1] = parent[ call[1] ];
							}
							Object(this.asset).addCallback.apply( this.asset, call );
							call[1] = oldCall1;
						}
					}
					else
					{
						oldCall1 = val[1];
						if( val[1] in this.jmlfunctions )
						{
							val[1] = this.jmlfunctions[ val[1] ];
						}
						else
						{
							val[1] = parent[val[1]];
						}
						Object(this.asset).addCallback.apply( this.asset, val );
						val[1] = oldCall1;
					}
				}
				else if( val is Array && key in this.jmlfunctions )
				{
					this.jmlfunctions[ key ].apply( null, val );
				}
				else if( key == "@call" )
				{
					for each( var call:Object in val )
					{
						for( var ckey:String in call )
						{
							var res:Array = this.resolve( ckey );
							param = call[ckey];
							for( var cf:int=res.length-1; cf>=1; --cf )
							{
								var prefix:String = res[cf].substr(0, 4);
								var postfix:String=res[cf].substr(4);
								switch( prefix )
								{
									case "new_":
										var nc:Class = ApplicationDomain.currentDomain.getDefinition(postfix) as Class;
										var nparam:Object = new nc();
										for( var nprop:String in param )
										{
											nparam[ nprop ] = param[ nprop ];
										}
										param = [nparam];
										break;
									case "set_":
										res[0][postfix] = param[0];
										break;
									case "":
										break;
									default:
										var tparam:Array = param.concat();
										for( var i:int=0; i<tparam.length; ++i )
										{
											if( tparam[i] in this.jmlconstants )
											{
												tparam[i] = this.jmlconstants[ tparam[i] ];
												// trace( tparam[i], tparam[i] = this.jmlconstants[ tparam[i] ] );
											}
										}
										
										if( this.asset is Sprite && res[0] == Sprite(this.asset).graphics )
										{
											if( res[cf] == "beginGradientFill" && (tparam as Array).length >= 5 )
											{
												var mat:Matrix = new Matrix();
												mat.createGradientBox.apply( mat, tparam[4] );
												tparam[4] = mat;
											}
										}
										if( res[0] == this.asset && res[cf] in this.jmlfunctions )
										{
											this.jmlfunctions[ res[cf] ].apply( null, tparam );
										}
										else
										{
											res[0][ res[cf] ].apply( res[0], tparam );
										}
										break;
								}
							}
						}
					}
				}
				else
				{
					trace("Could not assign", val, "to", key);
				}
			}
		}
	}
}